-
Notifications
You must be signed in to change notification settings - Fork 401
feat: initializeApp idempotency #2947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM! Added a few comments. Thank you!
src/app/lifecycle.ts
Outdated
* or `options.credential` are defined. This is due to the function's inability to | ||
* determine if the existing {@link App}'s `options` compare to the `options` parameter | ||
* of this function. It's recommended to use {@link getApp} or {@link getApps} if your | ||
* implementation uses either of these two fields in {@link AppOptions}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we extend the JSDoc with an example?
/**...
* For example, to safely initialize an app that may already exist:
*
* ```javascript
* let app;
* try {
* app = getApp("myApp");
* } catch (error) {
* app = initializeApp({ credential: myCredential }, "myApp");
* }
* ```
*/
src/app/lifecycle.ts
Outdated
} else if (autoInit) { | ||
// Auto-initialization is triggered when no options were passed to | ||
// initializeApp. With no options to compare, simply return the App. | ||
return currentApp; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we simplify this by using early exits/guard clauses (and removing the nested if else statements)?
If it doesn't work in this case feel free to ignore. For example:
if (this.appStore.has(appName)) {
const currentApp = this.appStore.get(appName)!;
if (currentApp.autoInit() !== autoInit) {
throw new FirebaseAppError(
AppErrorCodes.INVALID_APP_OPTIONS,
`Firebase app named "${appName}" attempted mismatch between custom AppOptions` +
' and an App created via Auto Init.'
)
}
if (autoInit) {
return currentApp;
}
if (typeof options.httpAgent !== 'undefined') {
.....
src/app/lifecycle.ts
Outdated
|
||
// `httpAgent` objects cannot be compared with deepEquals impls. | ||
// Idempotency cannot be supported if one exists. | ||
if (typeof options.httpAgent !== 'undefined') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Would it make sense to move this (and below) validation to a separate function (out of initializeApp
) to clean it up a bit?
src/app/lifecycle.ts
Outdated
* @internal | ||
*/ | ||
function validateAppNameFormat(appName: string): void { | ||
if (typeof appName !== 'string' || appName === '') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use validator.isNonEmptyString()
here?
firebase-admin-node/src/utils/validator.ts
Line 104 in 26b884f
export function isNonEmptyString(value: any): value is string { |
src/app/lifecycle.ts
Outdated
export function initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App { | ||
return defaultAppStore.initializeApp(options, appName); | ||
} | ||
|
||
/** | ||
* Returns an existing App instance for the provided name. If no name is provided | ||
* the the default app name is queried. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
queried
or used
?
* A (read-only) array of all initialized apps. | ||
* | ||
* @returns An array containing all initialized apps. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's get @egilmorez's review on the new doc changes, please. Thanks!
src/app/lifecycle.ts
Outdated
'name.', | ||
); | ||
AppErrorCodes.INVALID_APP_OPTIONS, | ||
`Firebase app named "${appName}" attempted mismatch between custom AppOptions` + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we simplify this error message? I am not sure Auto Init
is a well known term in the Admin SDK world
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks David!
Some things to look at, most particularly literals. Professor Gilmorez urges you to review https://docs.google.com/presentation/d/1Ezb4sp4XD6ItHISccnME-EVH_oJj59R5Naq0IskLrAo/edit?slide=id.g1cd7eabb964_0_10#slide=id.g1cd7eabb964_0_10.
src/app/firebase-app.ts
Outdated
|
||
/** | ||
* Returns `true` if the FirebaseApp instance was initialized with a custom | ||
* Credential. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd lowercase "credential" unless it's a literal.
And I would backtick FirebaseApp
as a literal.
src/app/lifecycle.ts
Outdated
options = loadOptionsFromEnvVar(); | ||
options.credential = getApplicationDefault(); | ||
} | ||
|
||
if (typeof appName !== 'string' || appName === '') { | ||
// Check if an App already exists and, if so, ensure its AppOptions match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of literals here too: "App
already exists and, if so, ensure its AppOptions
"
But maybe these comments don't get parsed for public docs; in that case, no big deal :)
src/app/lifecycle.ts
Outdated
* Validates that the `requestedOptions` and the `existingApp` options objects | ||
* do not have fields that would break deep equals comparisons. | ||
* | ||
* @param requestedOptions The incoming AppOptions of a new initailizeApp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the literals in these @tags need to be backticked? I would guess so.
src/app/lifecycle.ts
Outdated
export const defaultAppStore = new AppStore(); | ||
|
||
/** | ||
* Initializes the App instance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either backtick the literal, or just call it an "app instance."
src/app/lifecycle.ts
Outdated
* Note, due to the inablity to compare `http.Agent` objects and `Credential` objects, | ||
* this function cannot support idempotency if either of `options.httpAgent` or | ||
* `options.credential` are defined. When either is defined, subsequent invocations will | ||
* throw instead of returning an {@link App} object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be something like "throw an error," or cite the error name?
src/app/lifecycle.ts
Outdated
* the provided configuration. | ||
* | ||
* @throws FirebaseAppError if an {@link App} with the same name has already been | ||
* initialized with a different set of AppOptions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about the literals in these tagged descriptions:
- @param options - Optional A set of {@link AppOptions} for the {@link App} instance.
- If not present,
initializeApp
will try to initialize with the options from the FIREBASE_CONFIG
environment variable. If the environment variable contains a string- that starts with
{
it will be parsed as JSON, otherwise it will be assumed to be - pointing to a file.
- @param appName - Optional name of the FirebaseApp instance.
- @returns A new App instance, or the existing App if the instance already exists with
- the provided configuration.
- @throws FirebaseAppError if an {@link App} with the same name has already been
- initialized with a different set of AppOptions.
- @throws FirebaseAppError
src/app/lifecycle.ts
Outdated
export function initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App { | ||
return defaultAppStore.initializeApp(options, appName); | ||
} | ||
|
||
/** | ||
* Returns an existing App instance for the provided name. If no name is provided |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More questions about literals and backticks:
- @param options - Optional A set of {@link AppOptions} for the {@link App} instance.
- If not present,
initializeApp
will try to initialize with the options from the FIREBASE_CONFIG
environment variable. If the environment variable contains a string- that starts with
{
it will be parsed as JSON, otherwise it will be assumed to be - pointing to a file.
- @param appName - Optional name of the FirebaseApp instance.
- @returns A new App instance, or the existing App if the instance already exists with
- the provided configuration.
- @throws FirebaseAppError if an {@link App} with the same name has already been
- initialized with a different set of AppOptions.
- @throws FirebaseAppError
Discussion
Update
initializeApp
to return an existing app if one exists with the same name and the sameAppOptions
. However, due to their inabilty to be compared,initializeApp
will throw if an existing app has a configuredCredential
orhttpAgent
.Additionally adds reference docs to
getApp
andgetApps
.Testing
Updated unit tests.
API Changes
Internal: go/fb-admin-init-idempotency
Fixes: #2111